package com.xiaoyu.lib_av.proxy

import `in`.srain.cube.concurrent.AppThreads
import `in`.srain.cube.util.CLog
import `in`.srain.cube.util.internal.AppCallback
import `in`.srain.cube.util.internal.CodeException
import `in`.srain.cube.util.log.LogEvent
import android.content.Context
import android.os.PowerManager
import android.text.TextUtils
import android.view.ViewGroup
import com.xiaoyu.base.app.AppContext
import com.xiaoyu.base.app.GlobalUI
import com.xiaoyu.lib_av.datamodel.*
import com.xiaoyu.lib_av.datamodel.CallHangUpType.NEGATIVE
import com.xiaoyu.lib_av.datamodel.CallResponseType.BUSY_LINE
import com.xiaoyu.lib_av.log.AVLogClient
import com.xiaoyu.lib_av.manager.AgoraManager
import com.xiaoyu.lib_av.manager.CallManager
import io.agora.rtc.Constants.*
import io.agora.rtc.IRtcEngineEventHandler
import io.agora.rtc.RtcEngine
import io.agora.rtc.video.VideoCanvas
import io.agora.rtc.video.VideoEncoderConfiguration
import io.agora.rtc.video.VideoEncoderConfiguration.*
import io.agora.rtm.*
import java.util.*

class AgoraCallProxy : AgoraAbstractProxy(), ICallProxy {

    private val rtmCallManager: RtmCallManager? = rtmClient?.rtmCallManager
    private val fuidLocalInvitationMap = HashMap<String, LocalInvitation>()
    private val fuidRemoteInvitationMap = HashMap<String, RemoteInvitation>()

    private val rtmCallEventListener = object : RtmCallEventListener {
        override fun onLocalInvitationReceivedByPeer(localInvitation: LocalInvitation) {
            val params = CallParams.fromLocalInvitation(localInvitation)

            val logEvent = LogEvent("onLocalInvitationReceivedByPeer")
            logEvent.putData("params", params)
            AVLogClient.instance.addLog(logEvent)
        }

        override fun onLocalInvitationAccepted(localInvitation: LocalInvitation, response: String) {
            val params = CallParams.fromLocalInvitation(localInvitation)
            val logEvent = LogEvent("onLocalInvitationAccepted")
            logEvent.putData("params", params)
            logEvent.putData("response", response)
            AVLogClient.instance.addLog(logEvent)

            if (params.isInvalid) {
                return
            }
            val fuid = params.toUser.uid
            fuidLocalInvitationMap.remove(fuid)
            CallManager.instance.onCallResponded(fuid, CallResponseType.ACCEPT)
            mRtcEngine?.joinChannel(params.fromUser.token, params.channelId, null, Integer.valueOf(params.fromUser.uid))
        }

        override fun onLocalInvitationRefused(localInvitation: LocalInvitation, refuseType: String) {
            val params = CallParams.fromLocalInvitation(localInvitation)

            val logEvent = LogEvent("onLocalInvitationRefused")
            logEvent.putData("params", params)
            logEvent.putData("refuseType", refuseType)
            AVLogClient.instance.addLog(logEvent)

            if (params.isInvalid) {
                return
            }
            val fuid = params.toUser.uid
            fuidLocalInvitationMap.remove(fuid)
            callingIdSet.remove(fuid)
            when (refuseType) {
                CallRefuseType.BUSY_LINE -> CallManager.instance.onCallResponded(fuid, CallResponseType.BUSY_LINE)
                CallRefuseType.NORMAL -> CallManager.instance.onCallResponded(fuid, CallResponseType.REFUSE)
            }
        }

        override fun onLocalInvitationCanceled(localInvitation: LocalInvitation) {
            val params = CallParams.fromLocalInvitation(localInvitation)
            val logEvent = LogEvent("onLocalInvitationCanceled")
            logEvent.putData("params", params)
            AVLogClient.instance.addLog(logEvent)

            if (params.isInvalid) {
                return
            }
            val fuid = params.toUser.uid
            fuidLocalInvitationMap.remove(fuid)
            callingIdSet.remove(fuid)
            CallManager.instance.onCancelCall(fuid)
        }

        override fun onLocalInvitationFailure(localInvitation: LocalInvitation, errorCode: Int) {
            val params = CallParams.fromLocalInvitation(localInvitation)

            val logEvent = LogEvent("onLocalInvitationFailure")
            logEvent.putData("params", params)
            logEvent.putData("errorCode", errorCode)
            AVLogClient.instance.addLog(logEvent)

            tryToastInDevMode("Call rtm onLocalInvitationFailure() failure, code=${errorCode}")
            if (params.isInvalid) {
                return
            }
            val fuid = params.toUser.uid
            fuidLocalInvitationMap.remove(fuid)
            callingIdSet.remove(fuid)
            CallManager.instance.onCallFailed(fuid, CallFailType.fromAgoraLocalInviteCallError(errorCode))
        }

        override fun onRemoteInvitationReceived(remoteInvitation: RemoteInvitation) {
            val params = CallParams.fromRemoteInvitation(remoteInvitation)

            val powerManager = AppContext.getContext().getSystemService(Context.POWER_SERVICE) as PowerManager

            val logEvent = LogEvent("onRemoteInvitationReceived")
            logEvent.putData("params", params)
            logEvent.putData("interactive", powerManager.isInteractive)
            AVLogClient.instance.addLog(logEvent)

            if (params.isInvalid) {
                return
            }
            if (AgoraManager.getInstance().isCalling) {
                refuse(remoteInvitation, BUSY_LINE)
                return
            }
            val fuid = params.fromUser.uid
            CallManager.instance.onReceiveCall(fuid, params, Runnable {
                fuidRemoteInvitationMap[fuid] = remoteInvitation
                callingIdSet.add(fuid)
            })
        }

        override fun onRemoteInvitationAccepted(remoteInvitation: RemoteInvitation) {
            val params = CallParams.fromRemoteInvitation(remoteInvitation)

            val logEvent = LogEvent("onRemoteInvitationAccepted")
            logEvent.putData("params", params)
            AVLogClient.instance.addLog(logEvent)

            if (params.isInvalid) {
                return
            }
            val fuid = params.fromUser.uid
            fuidRemoteInvitationMap.remove(fuid)
            CallManager.instance.onRespondCall(fuid, CallResponseType.ACCEPT)
            mRtcEngine?.joinChannel(params.toUser.token, params.channelId, null, Integer.valueOf(params.toUser.uid))
        }

        override fun onRemoteInvitationRefused(remoteInvitation: RemoteInvitation) {
            val params = CallParams.fromRemoteInvitation(remoteInvitation)

            val logEvent = LogEvent("onRemoteInvitationRefused")
            logEvent.putData("params", params)
            AVLogClient.instance.addLog(logEvent)

            if (params.isInvalid) {
                return
            }
            val fuid = params.fromUser.uid
            fuidRemoteInvitationMap.remove(fuid)
            callingIdSet.remove(fuid)
            when (remoteInvitation.response) {
                CallRefuseType.NORMAL -> CallManager.instance.onRespondCall(fuid, CallResponseType.REFUSE)
                CallRefuseType.BUSY_LINE -> CallManager.instance.onRespondCall(fuid, CallResponseType.BUSY_LINE)
            }
        }

        override fun onRemoteInvitationCanceled(remoteInvitation: RemoteInvitation) {
            val params = CallParams.fromRemoteInvitation(remoteInvitation)
            val logEvent = LogEvent("onRemoteInvitationCanceled")
            logEvent.putData("params", params)
            AVLogClient.instance.addLog(logEvent)

            if (params.isInvalid) {
                return
            }
            val fuid = params.fromUser.uid
            fuidRemoteInvitationMap.remove(fuid)
            callingIdSet.remove(fuid)
            CallManager.instance.onCallCanceled(params)
        }

        override fun onRemoteInvitationFailure(remoteInvitation: RemoteInvitation, errorCode: Int) {
            val params = CallParams.fromRemoteInvitation(remoteInvitation)

            val logEvent = LogEvent("onRemoteInvitationFailure")
            logEvent.putData("params", params)
            logEvent.putData("errorCode", errorCode)
            AVLogClient.instance.addLog(logEvent)

            tryToastInDevMode("Call rtm onRemoteInvitationFailure() failure, code=${errorCode}")
            if (params.isInvalid) {
                return
            }
            val fuid = params.fromUser.uid
            fuidRemoteInvitationMap.remove(fuid)
            callingIdSet.remove(fuid)
            CallManager.instance.onCancelCall(fuid)
        }
    }

    override val rtcEngineEventHandler: IRtcEngineEventHandler = object : IRtcEngineEventHandler() {

        override fun onJoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
            AppThreads.runOnMainThread {
                val fuid = uid.toString()
                val logEvent = LogEvent("onJoinChannelSuccess")
                logEvent.putData("channel", channel)
                logEvent.putData("uid", uid)
                logEvent.putData("elapsed", elapsed)
                AVLogClient.instance.addLog(logEvent)

                CallManager.instance.onCallConnecting(fuid)
                fuidRemoteInvitationMap.remove(fuid)
                tryStartRecording()
            }
        }

        override fun onRejoinChannelSuccess(channel: String?, uid: Int, elapsed: Int) {
            super.onRejoinChannelSuccess(channel, uid, elapsed)
            val logEvent = LogEvent("onRejoinChannelSuccess")
            logEvent.putData("channel", channel)
            logEvent.putData("uid", uid)
            logEvent.putData("elapsed", elapsed)
            AVLogClient.instance.addLog(logEvent)
        }

        override fun onLeaveChannel(stats: RtcStats?) {
            val logEvent = LogEvent("onLeaveChannel")
            logEvent.putData("stats", stats?.totalDuration)
            AVLogClient.instance.addLog(logEvent)

            super.onLeaveChannel(stats)
            mRtcEngine?.stopAudioRecording()
        }

        override fun onUserJoined(uid: Int, elapsed: Int) {
            AppThreads.runOnMainThread {
                val fuid = uid.toString()
                val logEvent = LogEvent("onUserJoined")
                logEvent.putData("uid", uid)
                logEvent.putData("elapsed", elapsed)
                AVLogClient.instance.addLog(logEvent)

                CallManager.instance.onCallConnected(fuid)
            }
        }

        override fun onUserOffline(fuid: Int, reason: Int) {
            AppThreads.runOnMainThread {
                val logEvent = LogEvent("onUserOffline")
                logEvent.putData("fuid", fuid)
                logEvent.putData("reason", reason)
                AVLogClient.instance.addLog(logEvent)

                disconnect(fuid.toString(), NEGATIVE)
            }
        }

        override fun onNetworkQuality(uid: Int, txQuality: Int, rxQuality: Int) {
            AppThreads.runOnMainThread {
                val isTxQualityGood = txQuality == Quality.EXCELLENT || txQuality == Quality.GOOD
                val isRxQualityGood = rxQuality == Quality.EXCELLENT || rxQuality == Quality.GOOD
                val isQualityGood = isTxQualityGood && isRxQualityGood
                CallManager.instance.onNetStatusReported(isQualityGood, uid.toString())
                CLog.i(TAG, "onNetworkQuality(), uid: %s, isQualityGood: %s, txQuality=%s, rxQuality=%s", uid, isQualityGood, txQuality, rxQuality)
            }
        }

        override fun onAudioRouteChanged(routing: Int) {
            AppThreads.runOnMainThread {
                val headset = routing == AUDIO_ROUTE_HEADSET || routing == AUDIO_ROUTE_HEADSETBLUETOOTH || routing == AUDIO_ROUTE_HEADSETNOMIC
                CallManager.instance.onAudioDeviceChanged(headset)
                // CLog.i(TAG, "onAudioRouteChanged(), isHeadset: %s", headset)
            }
        }

        override fun onRtcStats(stats: RtcStats?) {
            AppThreads.runOnMainThread {
                CLog.i(TAG, "onRtcStats(), duration: %s", stats?.totalDuration)
                CallManager.instance.onStatus(stats?.totalDuration?.toLong() ?: 0)
            }
        }

        override fun onTokenPrivilegeWillExpire(token: String?) {
            super.onTokenPrivilegeWillExpire(token)
            mRtcEngine?.renewToken(token)
        }

        override fun onError(err: Int) {
            val logEvent = LogEvent("error")
            logEvent.putData("type", "rtc")
            logEvent.putData("code", err)
            AVLogClient.instance.addLog(logEvent)
            tryToastInDevMode("Call rtc error: code=$err")
        }

        override fun onLocalVideoStateChanged(state: Int, error: Int) {
            super.onLocalVideoStateChanged(state, error)
            val logEvent = LogEvent("onLocalVideoStateChanged")
            logEvent.putData("state", state)
            logEvent.putData("error", error)
            AVLogClient.instance.addLog(logEvent)

            if (state == LOCAL_VIDEO_STREAM_STATE_FAILED) {
                CallManager.instance.onLocalVideoFailure()
            }
        }

        override fun onRemoteVideoStateChanged(fuid: Int, state: Int, reason: Int, elapsed: Int) {
            AppThreads.runOnMainThread {
                val logEvent = LogEvent("onRemoteVideoStateChanged")
                logEvent.putData("fuid", fuid)
                logEvent.putData("state", state)
                logEvent.putData("reason", reason)
                logEvent.putData("elapsed", elapsed)
                AVLogClient.instance.addLog(logEvent)

                when {
                    state == REMOTE_VIDEO_STATE_STARTING -> {
                        CallManager.instance.onRemoteVideoReceived(fuid.toString())
                    }
                    state == REMOTE_VIDEO_STATE_STOPPED && reason == REMOTE_VIDEO_STATE_REASON_REMOTE_MUTED -> {
                        CallManager.instance.onRemoteVideoDisableStateChanged(fuid.toString(), true)
                    }
                    state == REMOTE_VIDEO_STATE_DECODING && reason == REMOTE_VIDEO_STATE_REASON_REMOTE_UNMUTED -> {
                        CallManager.instance.onRemoteVideoDisableStateChanged(fuid.toString(), false)
                    }
                }
            }
        }

        override fun onLocalAudioStateChanged(state: Int, error: Int) {
            super.onLocalAudioStateChanged(state, error)
            val logEvent = LogEvent("onLocalAudioStateChanged")
            logEvent.putData("state", state)
            logEvent.putData("error", error)
            AVLogClient.instance.addLog(logEvent)
        }

        override fun onRemoteAudioStateChanged(fuid: Int, state: Int, reason: Int, elapsed: Int) {
            AppThreads.runOnMainThread {
                val logEvent = LogEvent("onRemoteAudioStateChanged")
                logEvent.putData("fuid", fuid)
                logEvent.putData("state", state)
                logEvent.putData("reason", reason)
                AVLogClient.instance.addLog(logEvent)

                when {
                    state == REMOTE_AUDIO_STATE_STOPPED && reason == REMOTE_AUDIO_REASON_REMOTE_MUTED -> {
                        CallManager.instance.onRemoteAudioMuteStateChanged(fuid.toString(), true)
                    }
                    state == REMOTE_AUDIO_STATE_DECODING && reason == REMOTE_AUDIO_REASON_REMOTE_UNMUTED -> {
                        CallManager.instance.onRemoteAudioMuteStateChanged(fuid.toString(), false)
                    }
                }
            }
        }
    }

    init {
        rtmCallManager?.setEventListener(rtmCallEventListener)
    }

    override fun onCreate() {
        super.onCreate(CHANNEL_PROFILE_COMMUNICATION)
        AgoraManager.getInstance().setAgoraProxy(this)
    }

    override fun onDestroy() {
        AgoraManager.getInstance().setAgoraProxy(null)
        RtcEngine.destroy()
    }

    override fun isCalling(fuid: String?): Boolean {
        return AgoraManager.getInstance().isCalling(fuid)
    }

    override fun cancelCall(fuid: String?) {
        val localInvitation = fuidLocalInvitationMap[fuid] ?: return
        rtmCallManager?.cancelLocalInvitation(localInvitation, null)
    }

    override fun disconnect(fuid: String?, @CallHangUpType.HangUpTypeDef hangUpType: String) {
        fuid ?: return

        if (!AgoraManager.getInstance().isCalling(fuid)) {
            CLog.e(TAG, "disconnect() failure, user is not calling")
            return
        }
        mRtcEngine?.let {
            val resultCode = it.leaveChannel()
            if (resultCode == RTC_RESULT_OK) {
                callingIdSet.remove(fuid)
                AppThreads.runOnMainThread { CallManager.instance.onCallDisconnected(fuid, hangUpType) }
            }
        }
    }

    override fun setupLocalVideo(container: ViewGroup) {
        if (container.childCount > 0) {
            return
        }
        val videoConfig = VideoEncoderConfiguration(VD_640x480, FRAME_RATE.FRAME_RATE_FPS_15, 1000, ORIENTATION_MODE.ORIENTATION_MODE_ADAPTIVE)
        videoConfig.minFrameRate = FRAME_RATE.FRAME_RATE_FPS_10.value
        videoConfig.minBitrate = 400
        mRtcEngine?.setVideoEncoderConfiguration(videoConfig)
        mRtcEngine?.enableVideo()

        val surfaceView = RtcEngine.CreateRendererView(container.context)
        surfaceView.setZOrderMediaOverlay(true)
        container.addView(surfaceView)
        // 设置本地视图。
        mRtcEngine?.setupLocalVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_HIDDEN, 0))
    }

    override fun setupRemoteVideo(container: ViewGroup, fuid: String) {
        if (container.childCount > 0) {
            return
        }
        val surfaceView = RtcEngine.CreateRendererView(container.context)
        container.addView(surfaceView)
        mRtcEngine?.setupRemoteVideo(VideoCanvas(surfaceView, VideoCanvas.RENDER_MODE_HIDDEN, fuid.toInt()))
    }

    override fun response(fuid: String?, @CallResponseType.CallResponseTypeDef responseType: String) {
        if (TextUtils.equals(responseType, CallResponseType.ACCEPT)) {
            val remoteInvitation = fuidRemoteInvitationMap[fuid] ?: return
            rtmCallManager?.acceptRemoteInvitation(remoteInvitation, null)
        } else {
            callingIdSet.remove(fuid)
            val remoteInvitation = fuidRemoteInvitationMap[fuid] ?: return
            refuse(remoteInvitation, responseType)
        }
    }

    override fun sendMessage(message: CallMessage) {
        val rtmMessage = rtmClient?.createMessage()
        rtmMessage?.text = message.toJson().toString()

        val option = SendMessageOptions()
        rtmClient?.sendMessageToPeer(message.vendorUid, rtmMessage, option, object : ResultCallback<Void?> {
            override fun onSuccess(aVoid: Void?) {
                CallManager.instance.onMessageSend(message)
            }

            override fun onFailure(errorInfo: ErrorInfo?) {
                val logEvent = LogEvent("sendMessageToPeerError")
                logEvent.putData("code", errorInfo?.errorCode)
                logEvent.putData("desc", errorInfo?.errorDescription)
                AVLogClient.instance.addLog(logEvent)

                tryToastInDevMode("Call rtm sendMessageToPeer() failure, code=${errorInfo?.errorCode}, desc=${errorInfo?.errorDescription}")
            }
        })
    }

    override val isLocalCalling: Boolean
        get() = fuidLocalInvitationMap.isNotEmpty()

    private fun refuse(remoteInvitation: RemoteInvitation, @CallResponseType.CallResponseTypeDef responseType: String) {
        when (responseType) {
            CallResponseType.BUSY_LINE -> remoteInvitation.response = CallRefuseType.BUSY_LINE
            CallResponseType.REFUSE -> remoteInvitation.response = CallRefuseType.NORMAL
        }
        rtmCallManager?.refuseRemoteInvitation(remoteInvitation, null)
    }

    override fun call(params: CallParams, callback: AppCallback<Boolean>?) {
        val invitation = createInvitation(params)
        val logEvent = LogEvent("call")
        logEvent.putData("calleeId", invitation?.calleeId)
        logEvent.putData("channelId", invitation?.channelId)
        logEvent.putData("content", invitation?.content)
        AVLogClient.instance.addLog(logEvent)

        if (invitation == null) {
            tryToastInDevMode("Call rtm createAndSendInvitation() failure, invitation is null")
            callback?.onError(CodeException("Call rtm createAndSendInvitation() failure, invitation is null"))
            return
        }
        sendInvitation(invitation, params.toUser.uid, callback)
    }

    private fun createInvitation(params: CallParams): LocalInvitation? {
        val invitation = rtmCallManager?.createLocalInvitation(params.toUser.uid)
        if (invitation == null) {
            CLog.e(TAG, "call failure, invitation failure")
            GlobalUI.getInstance().showToast("呼叫失败, 模块未初始化")
            return null
        }
        invitation.channelId = params.channelId
        invitation.content = params.toJson().toString()
        return invitation
    }

    private fun sendInvitation(invitation: LocalInvitation, fuid: String, callback: AppCallback<Boolean>?) {
        rtmCallManager?.sendLocalInvitation(invitation, object : ResultCallback<Void?> {
            override fun onSuccess(aVoid: Void?) {
                fuidLocalInvitationMap[fuid] = invitation
                callingIdSet.add(fuid)
                callback?.onSuccess(true)
                CLog.i(TAG, "sendLocalInvitation() success")
            }

            override fun onFailure(errorInfo: ErrorInfo?) {
                tryToastInDevMode("Call rtm sendLocalInvitation() failure, code=${errorInfo?.errorCode}, desc=${errorInfo?.errorDescription}")
                callback?.onError(CodeException(errorInfo?.errorDescription
                        ?: "", errorInfo?.errorCode ?: 0))

                val logEvent = LogEvent("sendLocalInvitationFailure")
                logEvent.putData("code", errorInfo?.errorCode)
                logEvent.putData("desc", errorInfo?.errorDescription)
                AVLogClient.instance.addLog(logEvent)
            }
        })
    }
}
